/*
 * SelectableChannel.h
 *
 *  Created on: Jul 29, 2016
 *      Author: Lucifer
 */

#ifndef BFX_ACCEPTSOCKETCHANNEL_H_
#define BFX_ACCEPTSOCKETCHANNEL_H_

#include "./SelectableChannel.h"

namespace BFX {

class AcceptSocketChannel: public SelectableChannel {
public:
	AcceptSocketChannel() {
	}
	virtual ~AcceptSocketChannel() {
		BFX_ASSERT(isClosed());
	}

	/**
	 * @copydoc SelectableChannel::getValidOps()
	 * Accept socket channels only support the accepting of new connections.
	 * @return
	 */
	virtual int getValidOps() const {
		return SelectionKey::OP_ACCEPT;
	}

	/**
	 * @copydoc SelectableChannel::setReadyOps()
	 * @param ops
	 * @param key
	 * @return
	 */
	virtual bool setReadyOps(int ops, SelectionKey* key) {
		BFX_ASSERT(key);
		int oldOps = key->getReadyOps();
		int newOps = getValidOps() & ops;	// actually only OP_ACCEPT.
		key->setReadyOps(newOps);
		return (newOps & ~oldOps) != 0;
	}

	/**
	 * @copydoc SelectableChannel::getDescriptor()
	 * @return
	 */
	virtual fildes_t getDescriptor() const {
		return (_socket != nullptr) ? _socket->getDescriptor() : INVALID_FILDES;
	}

	bool isBound() const {
		return (nullptr != _socket);
	}

	/// Gets a server socket associated with this channel.
	Socket* getSocket() const {
		return _socket;
	}
	/**
	 * Binds the channel's socket to a local address and configures the socket to listen for
	 * connections.
	 * @param localAddr
	 * 		The local address to bind the socket.
	 * @param backlog
	 * 		The maximum number of pending connections
	 * @return
	 */
	bool bind(InetAddress* localAddr, int backlog) {
		BFX_ASSERT(!isClosed());
		BFX_ASSERT(!isBound());
		BFX_ASSERT(localAddr && localAddr->isValid());

		// The channel already disposed or still binding.
		if (!isClosed() || isBound()) {
			SocketError::set(SocketError::EC_IllegalStateError);
			return false;
		}

		// Create a socket and listen on specified local address.
		REF<Socket> acceptor = new Socket();
		if (acceptor->bind(localAddr) && acceptor->listen(backlog)) {
			BFX_ASSERT(_socket == NULL);
			acceptor->setNonBlocking(false);
			_socket = acceptor;
		}
		return (nullptr != _socket);
	}

	/**
	 * Accepts a connection made to this channel's socket.
	 * @param rSocket
	 * @return
	 */
	bool accept(Socket* rSocket) {
		BFX_ASSERT(rSocket);
		BFX_ASSERT(!isClosed());
		BFX_ASSERT(isBound());

		if (rSocket == nullptr) {
			SocketError::set(SocketError::EC_InvalidArgument);
			return false;
		}
		if (!isClosed() || !isBound()) {
			SocketError::set(SocketError::EC_IllegalStateError);
			return false;
		}

		// Accepts a new connection, and set it to blocking mode.
		if (!_socket->accept(rSocket, nullptr)) {
			return false;
		}
		return rSocket->setNonBlocking(false);
	}

protected:
	virtual void closeImpl() {
		SelectableChannel::closeImpl();

		// XXX should we need to shutdown the socket before close it?
		_socket = nullptr;
	}

private:
	REF<Socket>	_socket;
};

} /* namespace BFX */

#endif /* BFX_ACCEPTSOCKETCHANNEL_H_ */
